﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {

    /// <summary>
    /// モーフメッシュ・コンポーネント
    /// </summary>
    /// <remarks>
    /// ノードにアタッチ可能なコンポーネントの一種で、ノードにモーフ機能付きのメッシュを付加します。
    /// モーフィングは複数のメッシュをウェイト付きでブレンドして表示する機能でフェイシャルアニメーションなどに使用されます。
    /// DDDではCPUオンリーで実装されています。
    /// <para>
    /// モーフィングの仕組み。
    /// モーフィングは基本となるベースメッシュと、幾つかのターゲットメッシュをブレンドして計算します。
    /// 計算結果は基底クラスの<see cref="Mesh"/>の<see cref="VertexBuffer"/>に書き込まれます。
    /// 従って<see cref="Mesh.SetVertexBuffer(int,VertexBuffer)"/>で静的メッシュの<see cref=" VertexBuffer"/>を
    /// セットするのを忘れないようにしてください。
    /// </para>>
    /// </remarks>
    public class MorphMesh : Mesh {

        #region Field
        VertexBuffer baseTarget;
        VertexBuffer[] morphTargets;
        float[] morphWeights;
        #endregion

        #region Constructor
        /// <summary>
        /// サブメッシュ1つのコンストラクター
        /// </summary>
        /// <param name="numMorphTargets">モーフターゲットの個数</param>
        public MorphMesh (int numMorphTargets)
            : this (1, numMorphTargets) {
        }

        /// <summary>
        /// 複数のサブメッシュを持ったコンストラクター
        /// </summary>
        /// <param name="numSubmesh">サブメッシュの数</param>
        /// <param name="numMorphTargets">モーフ他ゲットの個数</param>
        public MorphMesh (int numSubmesh, int numMorphTargets)
            : base (numSubmesh) {
            if (numMorphTargets < 0) {
                throw new ArgumentException ("MoprhTargetCount is invalid");
            }
            this.baseTarget = null;
            this.morphTargets = new VertexBuffer[numMorphTargets];
            this.morphWeights = new float[numMorphTargets];
        }
        #endregion

        #region Property

        /// <summary>
        /// ベースターゲット
        /// </summary>
        /// <remarks>
        /// モーフィング処理の基本形となる<see cref="VertexBuffer"/>です。
        /// </remarks>
        public VertexBuffer BaseTarget {
            get { return baseTarget; }
        }

        /// <summary>
        /// モーフターゲットの個数
        /// </summary>
        public int MorphTargetCount {
            get { return morphTargets.Length; }
        }

        /// <summary>
        /// モーフターゲットの配列
        /// </summary>
        /// <remarks>
        /// </remarks>
        public IEnumerable<VertexBuffer> MorphTargets {
            get { return morphTargets; }
        }

        /// <summary>
        /// モーフウェイトの配列
        /// </summary>
        /// <remarks>
        /// このプロパティが<dic>IEnumerable{TValue}</dic>でないのはアニメーションのために書き換える必要があるから。
        /// </remarks>
        public float[] MorphWeights {
            get { return morphWeights; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.Add (baseTarget);
                objs.AddRange (morphTargets);
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// 基本ターゲットのセット
        /// </summary>
        /// <remarks>
        /// モーフィングの基本形となる<see cref="VertexBuffer"/>をセットします。
        /// 最終結果はこの基本形状とモーフタゲットの合成です。
        /// </remarks>
        /// <param name="baseTarget">ベースターゲット</param>
        public void SetBaseTarget (VertexBuffer baseTarget) {
            this.baseTarget = baseTarget;
        }

        /// <summary>
        /// モーフターゲットをセットする
        /// </summary>
        /// この段階ではウェイト値はデフォルト(=1.0)です。
        /// <param name="index">モーフターゲット番号</param>
        /// <param name="target">モーフターゲット</param>
        public void SetMorphTarget (int index, VertexBuffer target) {
            if (index < 0 || index >= MorphTargetCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }

            this.morphTargets[index] = target;
        }

        /// <summary>
        /// i番目のモーフウェイトをセットします
        /// </summary>
        /// <remarks>
        /// i番目のモーフターゲットのウェイトをセットします。
        /// </remarks>
        /// <param name="index">インデックス</param>
        /// <param name="weight">ウェイト値</param>
        public void SetMorphWeight (int index, float weight) {
            if (index < 0 || index >= MorphTargetCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.morphWeights[index] = weight;
        }

        /// <summary>
        /// 全てのモーフウェイトをセットします
        /// </summary>
        /// <remarks>
        /// ウェイトは全ターゲットに対して1度に設定します。
        /// 引数のウェイトの配列は少なくともターゲット個数分必要です。
        /// ウェイト値には[0,1]だけでなく任意の値を設定できます。
        /// </remarks>
        /// <param name="weights">ウェイトの配列</param>
        public void SetMorphWeights (float[] weights) {
            if (weights == null) {
                throw new ArgumentNullException ("Weights is null");
            }
            if (weights.Length < MorphTargetCount) {
                throw new ArgumentException ("Weights is too short");
            }

            for (int i = 0; i < weights.Length; i++) {
                this.morphWeights[i] = weights[i];
            }
        }

        /// <summary>
        /// モーフィングを行い結果を静的メッシュに反映します
        /// </summary>
        /// <remarks>
        /// ベースメッシュとモーフメッシュをウェイト値に従ってブレンドし、
        /// 基底クラスの静的メッシュに反映します。
        /// </remarks>
        public void ApplyMorphing () {
            throw new NotFiniteNumberException ("Sorry");
        }

        /// <summary>
        /// モーフウェイトの取得
        /// </summary>
        /// <param name="index">モーフインデックス</param>
        /// <returns></returns>
        public float GetMorphWeight (int index) {
            if (index < 0 || index >= MorphTargetCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return morphWeights[index];
        }

        /// <summary>
        /// モーフターゲットの取得
        /// </summary>
        /// <param name="index">モーフインデックス</param>
        /// <returns></returns>
        public VertexBuffer GetMorphTarget (int index) {
            if (index < 0 || index >= MorphTargetCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return morphTargets[index];
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary> 
        public override string ToString () {
            return string.Format ("MorphMesh: , MorphTargets={0}", MorphTargetCount);
        }

        #endregion



    }
}
